package net.dfx.iot_download_tool.save;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.math.BigDecimal;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;

import org.apache.commons.lang3.StringUtils;
import org.apache.hc.client5.http.classic.methods.HttpGet;
import org.apache.hc.client5.http.config.ConnectionConfig;
import org.apache.hc.client5.http.config.RequestConfig;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder;
import org.apache.hc.client5.http.ssl.DefaultClientTlsStrategy;
import org.apache.hc.client5.http.ssl.NoopHostnameVerifier;
import org.apache.hc.core5.http.HttpEntity;
import org.apache.hc.core5.http.io.HttpClientResponseHandler;
import org.apache.hc.core5.http.io.entity.EntityUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.usermodel.WorkbookFactory;
import org.apache.poi.ss.util.CellRangeAddress;

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.support.ExcelTypeEnum;
import com.alibaba.excel.write.builder.ExcelWriterBuilder;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;

import net.dfx.iot_download_tool.utils.ApiKeyUtil;
import net.dfx.iot_download_tool.utils.SSLUtil;
import net.dfx.iot_download_tool.utils.TrustAllHostnameVerifier;
import net.dfx.iot_download_tool.vo.ApikeyVO;
import net.dfx.iot_download_tool.vo.IotDataVO;
import net.dfx.iot_download_tool.vo.ManyParamVO;
import net.dfx.iot_download_tool.vo.MutileDataVO;
import net.dfx.iot_download_tool.vo.StartAndEndTime;

import org.apache.poi.xssf.streaming.SXSSFWorkbook;

public class ExecUtile {
	static ZoneId chinaZone = ZoneId.of("Asia/Shanghai");
	private static final int SUB_THREADS_PER_MAIN = 2; // 每个主线程可开启的子线程数
	private static final int TOTAL_THREADS = 8; // 主线程数
	private static final int TOTAL_REQUESTS = 23; // 总请求数目，用于主线程划分任务子集
	private static final int CONNECTION_POOL_SIZE = 140; // 连接池总大小
	private static final int CONNECTIONS_PER_HOST = 20; // 每主机最大连接数
	private static final int CONNECT_TIMEOUT = 520000; // 连接超时(ms)
	private static final int RESPONSE_TIMEOUT = 480000; // 响应超时(ms)
	static Logger logger = LogManager.getLogger(ExecUtile.class);
	private static final CloseableHttpClient httpClient;

	static {
		httpClient = createUnsafeHttpClient();
	}
	public void execGet() {
		logger.info("开始执行IoT数据导出");
		ApiKeyUtil keyUtil = new ApiKeyUtil();
		ApikeyVO configVO = keyUtil.getApiKey();
		if (configVO!=null) {
			if (StringUtils.isNotBlank(configVO.getApikey())) {
				oneEquipMoreParamsOneMonth(configVO);
			}
		}
	}

	/**
	 * 一个设备，多个参数，一个月时间，一个个sheet页放全部参数
	 * 
	 * @param configVO
	 */
	private void oneEquipMoreParamsOneMonth(ApikeyVO configVO) {
		Scanner sc = new Scanner(System.in);
		// 获取用户主目录
		String userHome = System.getProperty("user.home");
		// 拼接下载目录路径
		String pathPre = userHome + File.separator + "Downloads";
		System.out.println("请输入excel文件要保存到的文件夹，或者直接回车，默认保存到 下载 目录");
		String pathPreT = sc.nextLine();
		if (StringUtils.isNotBlank(pathPreT)) {
			pathPre = pathPreT;
		}
		// 特殊需求改造
		List<String> tagStrs = new ArrayList<>();
		tagStrs.add("蜡桶1区实际温度");
		tagStrs.add("蜡桶2区实际温度");
		tagStrs.add("合模板实际温度");
		tagStrs.add("台面实际温度");
		tagStrs.add("压蜡缸实际温度");
		tagStrs.add("输蜡管实际温度");
		tagStrs.add("射嘴体实际温度");
		tagStrs.add("液压系统实际温度");
		tagStrs.add("合模压力");
		tagStrs.add("注射时间");
		tagStrs.add("保持时间");
		tagStrs.add("注射流量");
		tagStrs.add("注射压力");
		tagStrs.add("急停");
		tagStrs.add("自动手动");
		tagStrs.add("蜡桶液位高限");
		tagStrs.add("射嘴伸出开关");
		tagStrs.add("泵站电机运行");
		tagStrs.add("搅拌电机运行");
		tagStrs.add("通道状态");
		tagStrs.add("设备状态");
		tagStrs.add("自动运行");
		tagStrs.add("水箱温度");

		String fileNameStr = "西安二十五T压蜡机";

		// 设置开始时间、结束时间集合
		StartAndEndTime saet = new StartAndEndTime("2025-08-28 00:00:01", "2025-09-28 23:59:59", "20250814");

		try {
			// 按照参数个数启动线程
			logger.info("启动多个线程处理参数请求");
			ExecutorService executor = Executors.newFixedThreadPool(TOTAL_THREADS);
			Future<List<MutileDataVO>>[] futures = new Future[TOTAL_THREADS];
			// 完整文件路径
			String fileTmpPathT = pathPre + File.separator + fileNameStr;
			// 提交 HTTPS 请求任务到线程池
			int totalNum = tagStrs.size();
			int subNum = totalNum % TOTAL_THREADS;
			int oneNum = (totalNum - subNum) / TOTAL_THREADS;
			int loopNum = TOTAL_THREADS - 1;
			for (int i = 0; i < loopNum; i++) {
				String threadStr = "-"+i+"-";
				int tagStart = i * oneNum;
				int tagEnd = tagStart + oneNum;
				List<String> subTagStrs = tagStrs.subList(tagStart, tagEnd);
				Callable<List<MutileDataVO>> task = () -> sendHttpsRequest3(fileNameStr, subTagStrs, fileTmpPathT, threadStr,
						configVO, saet);
				futures[i] = executor.submit(task);
			}
			String threadStr = "-"+loopNum+"-";
			int tagStart = loopNum * oneNum;
			int tagEnd = tagStart + oneNum + subNum;
			List<String> subEndTagStrs = tagStrs.subList(tagStart, tagEnd);
			Callable<List<MutileDataVO>> task = () -> sendHttpsRequest3(fileNameStr, subEndTagStrs, fileTmpPathT, threadStr,
					configVO, saet);
			futures[loopNum] = executor.submit(task);

			// 等待所有任务完成并收集结果
			List<MutileDataVO> allSheetDatas = new ArrayList<>();
			for (Future<List<MutileDataVO>> future : futures) {
				if (future != null) {
					try {
						List<MutileDataVO> tableData = future.get();
						if (tableData != null && !tableData.isEmpty()) {
							allSheetDatas.addAll(tableData);
						}
					} catch (Exception e) {
						logger.error("==主分支进程开启并执行子线程异常==", e);
					}
				}
			}
			// 关闭线程池（不再接受新任务）
			executor.shutdown();

			// 遍历解析所有响应
			String tmpPath = fileTmpPathT + "_临时.xlsx";
			String endPath = fileTmpPathT + ".xlsx";
			logger.info("开始写入excel文件");
			// 写一个sheet页
			generateBaseExcel2(allSheetDatas, tmpPath);
		} catch (Exception e) {
			logger.error("创建主线程失败", e);
		}
	}
	private void oneEquipMoreParamsOneMonth2(ApikeyVO configVO) {
		// 获取用户主目录
		String userHome = System.getProperty("user.home");
		// 拼接下载目录路径
		String pathPre = userHome + File.separator + "Downloads";
		// 特殊需求改造
		List<String> tagStrs = new ArrayList<>();
		tagStrs.add("设备状态");
		tagStrs.add("自动运行");
		tagStrs.add("注射时间");
		tagStrs.add("注射流量");
		tagStrs.add("保压时间");
		tagStrs.add("注射位置");
		tagStrs.add("合膜高度");
		tagStrs.add("慢速合膜高度");
		tagStrs.add("打开位置");
		tagStrs.add("快速打开位置");
		tagStrs.add("模板位置");
		tagStrs.add("模板压力");
		tagStrs.add("蜡缸1区温度");
		tagStrs.add("蜡缸2区温度");
		tagStrs.add("喷嘴温度");
		tagStrs.add("注蜡管温度");
		tagStrs.add("注射单元温度");
		tagStrs.add("设备在线");
		tagStrs.add("上模板温度");
		tagStrs.add("下模板温度");
		tagStrs.add("注射压力");

		String fileNameStr = "西安蜡模压注机";

		// 设置开始时间、结束时间集合
		StartAndEndTime saet = new StartAndEndTime("2025-08-28 00:00:01", "2025-09-28 23:59:59", "20250814");

		try {
			// 按照参数个数启动线程
			logger.info("启动多个线程处理参数请求");
			ExecutorService executor = Executors.newFixedThreadPool(TOTAL_THREADS);
			Future<List<MutileDataVO>>[] futures = new Future[TOTAL_THREADS];
			// 完整文件路径
			String fileTmpPathT = pathPre + File.separator + fileNameStr;
			// 提交 HTTPS 请求任务到线程池
			int totalNum = tagStrs.size();
			int subNum = totalNum % TOTAL_THREADS;
			int oneNum = (totalNum - subNum) / TOTAL_THREADS;
			int loopNum = TOTAL_THREADS - 1;
			for (int i = 0; i < loopNum; i++) {
				String threadStr = "-"+i+"-";
				int tagStart = i * oneNum;
				int tagEnd = tagStart + oneNum;
				List<String> subTagStrs = tagStrs.subList(tagStart, tagEnd);
				Callable<List<MutileDataVO>> task = () -> sendHttpsRequest3(fileNameStr, subTagStrs, fileTmpPathT, threadStr,
						configVO, saet);
				futures[i] = executor.submit(task);
			}
			String threadStr = "-"+loopNum+"-";
			int tagStart = loopNum * oneNum;
			int tagEnd = tagStart + oneNum + subNum;
			List<String> subEndTagStrs = tagStrs.subList(tagStart, tagEnd);
			Callable<List<MutileDataVO>> task = () -> sendHttpsRequest3(fileNameStr, subEndTagStrs, fileTmpPathT, threadStr,
					configVO, saet);
			futures[loopNum] = executor.submit(task);

			// 等待所有任务完成并收集结果
			List<MutileDataVO> allSheetDatas = new ArrayList<>();
			for (Future<List<MutileDataVO>> future : futures) {
				if (future != null) {
					try {
						List<MutileDataVO> tableData = future.get();
						if (tableData != null && !tableData.isEmpty()) {
							allSheetDatas.addAll(tableData);
						}
					} catch (Exception e) {
						logger.error("==主分支进程开启并执行子线程异常==", e);
					}
				}
			}
			// 关闭线程池（不再接受新任务）
			executor.shutdown();

			// 遍历解析所有响应
			String tmpPath = fileTmpPathT + "_临时.xlsx";
			String endPath = fileTmpPathT + ".xlsx";
			logger.info("开始写入excel文件");
			// 写一个sheet页
			for (MutileDataVO mudVO : allSheetDatas) {
				generateBaseExcel(mudVO, tmpPath);
				logger.info("临时excel文件写入完成");
//				try {
//					processMergedCells(tmpPath, endPath);
//					logger.info("Excel is OK");
//				} catch (Exception e) {
//					logger.error("==主分支进程遍历解析所有响应异常==", e);
//				}
			}
		} catch (Exception e) {
			logger.error("创建主线程失败", e);
		}
	}
	private void oneEquipMoreParamsOneMonth3(ApikeyVO configVO) {
		// 获取用户主目录
		String userHome = System.getProperty("user.home");
		// 拼接下载目录路径
		String pathPre = userHome + File.separator + "Downloads";
		// 特殊需求改造
		List<String> tagStrs = new ArrayList<>();
		tagStrs.add("设备状态");
		tagStrs.add("自动运行");
		tagStrs.add("注射时间");
		tagStrs.add("流量");
		tagStrs.add("保压时间");
		tagStrs.add("注射位置");
		tagStrs.add("合膜高度");
		tagStrs.add("慢速合膜高度");
		tagStrs.add("打开位置");
		tagStrs.add("快速打开位置");
		tagStrs.add("模板位置");
		tagStrs.add("模板压力");
		tagStrs.add("蜡缸1区温度");
		tagStrs.add("蜡缸2区温度");
		tagStrs.add("喷嘴温度");
		tagStrs.add("注蜡管温度");
		tagStrs.add("注射单元温度");
		tagStrs.add("上模板温度");
		tagStrs.add("下模板温度");
		tagStrs.add("注射压力");

		String fileNameStr = "西安压蜡机";

		// 设置开始时间、结束时间集合
		StartAndEndTime saet = new StartAndEndTime("2025-08-28 00:00:01", "2025-09-28 23:59:59", "20250814");

		try {
			// 按照参数个数启动线程
			logger.info("启动多个线程处理参数请求");
			ExecutorService executor = Executors.newFixedThreadPool(TOTAL_THREADS);
			Future<List<MutileDataVO>>[] futures = new Future[TOTAL_THREADS];
			// 完整文件路径
			String fileTmpPathT = pathPre + File.separator + fileNameStr;
			// 提交 HTTPS 请求任务到线程池
			int totalNum = tagStrs.size();
			int subNum = totalNum % TOTAL_THREADS;
			int oneNum = (totalNum - subNum) / TOTAL_THREADS;
			int loopNum = TOTAL_THREADS - 1;
			for (int i = 0; i < loopNum; i++) {
				String threadStr = "-"+i+"-";
				int tagStart = i * oneNum;
				int tagEnd = tagStart + oneNum;
				List<String> subTagStrs = tagStrs.subList(tagStart, tagEnd);
				Callable<List<MutileDataVO>> task = () -> sendHttpsRequest3(fileNameStr, subTagStrs, fileTmpPathT, threadStr,
						configVO, saet);
				futures[i] = executor.submit(task);
			}
			String threadStr = "-"+loopNum+"-";
			int tagStart = loopNum * oneNum;
			int tagEnd = tagStart + oneNum + subNum;
			List<String> subEndTagStrs = tagStrs.subList(tagStart, tagEnd);
			Callable<List<MutileDataVO>> task = () -> sendHttpsRequest3(fileNameStr, subEndTagStrs, fileTmpPathT, threadStr,
					configVO, saet);
			futures[loopNum] = executor.submit(task);

			// 等待所有任务完成并收集结果
			List<MutileDataVO> allSheetDatas = new ArrayList<>();
			for (Future<List<MutileDataVO>> future : futures) {
				if (future != null) {
					try {
						List<MutileDataVO> tableData = future.get();
						if (tableData != null && !tableData.isEmpty()) {
							allSheetDatas.addAll(tableData);
						}
					} catch (Exception e) {
						logger.error("==主分支进程开启并执行子线程异常==", e);
					}
				}
			}
			// 关闭线程池（不再接受新任务）
			executor.shutdown();

			// 遍历解析所有响应
			String tmpPath = fileTmpPathT + "_临时.xlsx";
			String endPath = fileTmpPathT + ".xlsx";
			logger.info("开始写入excel文件");
			// 写一个sheet页
			for (MutileDataVO mudVO : allSheetDatas) {
				generateBaseExcel(mudVO, tmpPath);
				logger.info("临时excel文件写入完成");
//				try {
//					processMergedCells(tmpPath, endPath);
//					logger.info("Excel is OK");
//				} catch (Exception e) {
//					logger.error("==主分支进程遍历解析所有响应异常==", e);
//				}
			}
		} catch (Exception e) {
			logger.error("创建主线程失败", e);
		}
	}

	/**
	 * 一个设备，多个参数，每个sheet？？
	 * 
	 * @param configVO
	 */
	private void oneEquipMoreParamsOneDay(ApikeyVO configVO) {
		Scanner sc = new Scanner(System.in);
		// 获取用户主目录
		String userHome = System.getProperty("user.home");
		// 拼接下载目录路径
		String pathPre = userHome + File.separator + "Downloads";
		System.out.println("请输入excel文件要保存到的文件夹，或者直接回车，默认保存到 下载 目录");
		String pathPreT = sc.nextLine();
		if (StringUtils.isNotBlank(pathPreT)) {
			pathPre = pathPreT;
		}
		// 特殊需求改造
		List<String> tagStrs = new ArrayList<>();
		tagStrs.add("蜡桶1区实际温度");
		tagStrs.add("蜡桶2区实际温度");
		tagStrs.add("合模板实际温度");
		tagStrs.add("台面实际温度");
		tagStrs.add("压蜡缸实际温度");
		tagStrs.add("输蜡管实际温度");
		tagStrs.add("射嘴体实际温度");
		tagStrs.add("液压系统实际温度");
		tagStrs.add("合模压力");
		tagStrs.add("注射时间");
		tagStrs.add("保持时间");
		tagStrs.add("注射流量");
		tagStrs.add("注射压力");
		tagStrs.add("急停");
		tagStrs.add("自动手动");
		tagStrs.add("蜡桶液位高限");
		tagStrs.add("射嘴伸出开关");
		tagStrs.add("泵站电机运行");
		tagStrs.add("搅拌电机运行");
		tagStrs.add("通道状态");
		tagStrs.add("设备状态");
		tagStrs.add("自动运行");
		tagStrs.add("水箱温度");
		// tagStrs.add("");
		// tagStrs.add("");
		// tagStrs.add("");
		// tagStrs.add("");
		// tagStrs.add("");
		// tagStrs.add("");

		String fileNameStr = "西安二十五T压蜡机";

		// 设置开始时间、结束时间集合
		List<StartAndEndTime> saets = new ArrayList<>();

		StartAndEndTime saet_1 = new StartAndEndTime("2025-08-14 00:00:01", "2025-08-14 23:59:59", "20250814");
		saets.add(saet_1);
		StartAndEndTime saet_2 = new StartAndEndTime("2025-08-22 00:00:01", "2025-08-22 23:59:59", "20250822");
		saets.add(saet_2);
		StartAndEndTime saet_3 = new StartAndEndTime("2025-08-23 00:00:01", "2025-08-23 23:59:59", "20250823");
		saets.add(saet_3);
		StartAndEndTime saet_4 = new StartAndEndTime("2025-08-26 00:00:01", "2025-08-26 23:59:59", "20250826");
		saets.add(saet_4);
		StartAndEndTime saet_5 = new StartAndEndTime("2025-08-29 00:00:01", "2025-08-29 23:59:59", "20250829");
		saets.add(saet_5);
		StartAndEndTime saet_6 = new StartAndEndTime("2025-09-02 00:00:01", "2025-09-02 23:59:59", "20250902");
		saets.add(saet_6);
		StartAndEndTime saet_7 = new StartAndEndTime("2025-09-04 00:00:01", "2025-09-04 23:59:59", "20250904");
		saets.add(saet_7);
		StartAndEndTime saet_8 = new StartAndEndTime("2025-09-05 00:00:01", "2025-09-05 23:59:59", "20250905");
		saets.add(saet_8);
		StartAndEndTime saet_9 = new StartAndEndTime("2025-09-09 00:00:01", "2025-09-09 23:59:59", "20250909");
		saets.add(saet_9);
		try {
			// 启动多个线程分别处理一天的请求
			logger.info("启动多个线程分别处理请求");
			ExecutorService executor = Executors.newFixedThreadPool(9);
			Future<Boolean>[] futures = new Future[9];
			// 完整文件路径
			String fileTmpPathT = pathPre + File.separator + fileNameStr;
			// 提交 HTTPS 请求任务到线程池
			for (int i = 0; i < 9; i++) {
				StartAndEndTime saet = saets.get(i);
				Callable<Boolean> task = () -> sendHttpsRequest(fileNameStr, tagStrs, fileTmpPathT, saet, configVO);
				futures[i] = executor.submit(task);
			}
			// 等待所有任务完成并收集结果
			boolean allSucFlag = true;

			for (Future<Boolean> future : futures) {
				Boolean sheetData = future.get();
				if (!sheetData) {
					allSucFlag = false;
					break;
				}
			}
			// 关闭线程池（不再接受新任务）
			executor.shutdown();
			if (allSucFlag) {
				logger.info("数据导出全部成功！");
			} else {
				logger.info("数据导出部分失败！");
			}

		} catch (Exception e) {
			logger.error("创建主线程失败", e);
		}
	}

	/**
	 * 按照要创建的excel数量，创建线程，可以视为是主线程，由于数据量太大，改成一个参数一个sheet页，返回的是sheet页的集合
	 *
	 * @param equipName 设备名称字符串
	 * @param tagStrs   设备的所有标签
	 * @param tmpPathT  带有设备名称的路径
	 * @param saet      设备信息
	 * @param configVO  配置类
	 * @return {@link List}{@code <}{@link ManyParamVO}{@code >} sheet的数据
	 */
	private List<MutileDataVO> sendHttpsRequest3(String equipName, List<String> tagStrs, String tmpPathT,
												String mainThreadNo, ApikeyVO configVO, StartAndEndTime saet) {

		logger.info("主线程-" + mainThreadNo + "-执行中……");

		List<MutileDataVO> allSheetDatas = new ArrayList<>();
		for (String tagStr : tagStrs) {
			// 格式化时间为时间戳
			DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
			// 解析终止时间
			LocalDateTime endDT = LocalDateTime.parse(saet.getEndT(), dtf);
			ZonedDateTime zedt = endDT.atZone(chinaZone);// 范围终止时间的时间戳
			Instant intet = zedt.toInstant();
			long endSec = intet.getEpochSecond();

			// 计算一个月前的时间戳和天数差
			ZonedDateTime startDT = zedt.minusMonths(3);
			Instant intst = startDT.toInstant();
			long startSec = intst.getEpochSecond();

			// 2. 计算天数差（按照日历计算，不足一天按一天算）
			long totalSeconds = Math.abs(endSec - startSec);
			int daysNum = (int) Math.ceil(Math.abs(totalSeconds) / (24.0 * 60 * 60));

			// 循环内发起请求
			String url = "https://10.101.1.200/reader/api/v1/historyDataByTag";

			ExecutorService subExecutor = Executors.newFixedThreadPool(SUB_THREADS_PER_MAIN);
			Future<List<ManyParamVO>>[] subFutures = new Future[SUB_THREADS_PER_MAIN];

			// 分发N个1天请求到主线程的子线程，最终回收一个参数的一个月的数据集
			int subNum = daysNum % SUB_THREADS_PER_MAIN;
			int oneNum = (daysNum - subNum) / SUB_THREADS_PER_MAIN;
			int loopNum = SUB_THREADS_PER_MAIN - 1;
			for (int i = 0; i < loopNum; i++) {
				int betweenDay = (i + 1) * oneNum;
				long finalStartSec = startSec;
				Callable<List<ManyParamVO>> subTask = () -> executeSingleRequest3(equipName, tagStr, url, configVO,
						finalStartSec, betweenDay);
				subFutures[i] = subExecutor.submit(subTask);
				// 变更起始时间
				startSec += betweenDay * 24 * 60 * 60+1;
			}
			long lastStartSec = startSec;
			int lastBetDay = oneNum + subNum;
			Callable<List<ManyParamVO>> subEndTask = () -> executeSingleRequest3(equipName, tagStr, url, configVO,
					lastStartSec, lastBetDay);
			subFutures[loopNum] = subExecutor.submit(subEndTask);

            // 收集所有子线程的结果集到一个集合中
			List<ManyParamVO> mapVOs = new ArrayList<>();
			for (Future<List<ManyParamVO>> future : subFutures) {
				if (future != null) {
					try {
						List<ManyParamVO> tableData = future.get();
						if (tableData != null && !tableData.isEmpty()) {
							mapVOs.addAll(tableData);
						}
					} catch (Exception e) {
						logger.error("==主分支进程开启并执行子线程异常==", e);
					}
				}
			}
			// 关闭线程池（不再接受新任务）
			subExecutor.shutdown();

			// 组装当前参数一个月的数据量
			MutileDataVO mudVO = new MutileDataVO();
			if (!mapVOs.isEmpty()) {
				logger.info("执行表格数据处理……");
				// 自定义表头
				List<List<String>> heads = new ArrayList<>();
				// 添加表头行：tagName + null
				for (ManyParamVO mP : mapVOs) {
					List<String> h_1 = new ArrayList<>();
					h_1.add(mP.getTagName());
					h_1.add("时间");
					heads.add(h_1);
					List<String> h_2 = new ArrayList<>();
					h_2.add("");
					h_2.add("数值");
					heads.add(h_2);
				}
				mudVO.setHeads(heads);

				// 纯数据表最大行数
				int rownum = 0;
				for (ManyParamVO manyParam : mapVOs) {
					int n = manyParam.getDataVo().size();
					if (rownum < n) {
						int tmp = rownum;
						rownum = n;
						n = tmp;
					}
				}

				List<List<Object>> afterAligns = alignColumns(mapVOs, rownum);
				mudVO.setAfterAligns(afterAligns);
				logger.info("表格数据封装完成");
			}
			mudVO.setSheetName(tagStr);
			allSheetDatas.add(mudVO);
		}

		return allSheetDatas;
	}

	/**
	 * 按照要创建的excel数量，创建线程，可以视为是主线程
	 * 
	 * @param equipName 设备名称字符串
	 * @param tagStrs   设备的所有标签
	 * @param tmpPathT  带有设备名称的路径
	 * @param saet      设备信息
	 * @param configVO  配置类
	 * @return {@link List}{@code <}{@link ManyParamVO}{@code >} sheet的数据
	 */
	private List<ManyParamVO> sendHttpsRequest2(String equipName, List<String> tagStrs, String tmpPathT,
			String mainThreadNo, ApikeyVO configVO, StartAndEndTime saet) {
		
		logger.info("主线程-" + mainThreadNo + "-执行中……");

		List<ManyParamVO> allSheetDatas = new ArrayList<>();
		for (String tagStr : tagStrs) {
			// 格式化时间为时间戳
			DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
			// 解析终止时间
			LocalDateTime endDT = LocalDateTime.parse(saet.getEndT(), dtf);
			ZonedDateTime zedt = endDT.atZone(chinaZone);// 范围终止时间的时间戳
			Instant intet = zedt.toInstant();
			long endSec = intet.getEpochSecond();

			// 计算一个月前的时间戳和天数差
			ZonedDateTime startDT = zedt.minusMonths(1);
			Instant intst = startDT.toInstant();
			long startSec = intst.getEpochSecond();

			// 2. 计算天数差（按照日历计算，不足一天按一天算）
			long totalSeconds = Math.abs(endSec - startSec);
			int daysNum = (int) Math.ceil(Math.abs(totalSeconds) / (24.0 * 60 * 60));

			// 循环内发起请求
			String url = "https://10.101.1.200/reader/api/v1/historyDataByTag";

			ExecutorService subExecutor = Executors.newFixedThreadPool(SUB_THREADS_PER_MAIN);
			Future<List<IotDataVO>>[] subFutures = new Future[SUB_THREADS_PER_MAIN];

			// 分发N个1天请求到主线程的子线程，最终回收一个参数的一个月的数据集
			int subNum = daysNum % SUB_THREADS_PER_MAIN;
			int oneNum = (daysNum - subNum) / SUB_THREADS_PER_MAIN;
			int loopNum = SUB_THREADS_PER_MAIN - 1;
			for (int i = 0; i < loopNum; i++) {
				int betweenDay = (i + 1) * oneNum;
				long subStartSec = startSec + (i + i * (i - 1) / 2) * oneNum * 24 * 60 * 60;
				Callable<List<IotDataVO>> subTask = () -> executeSingleRequest2(equipName, tagStr, url, configVO,
						subStartSec, betweenDay);
				subFutures[i] = subExecutor.submit(subTask);
				// 变更起始时间
				startSec += betweenDay * oneNum;
			}
			long lastStartSec = startSec + (loopNum + loopNum * (loopNum - 1) / 2) * oneNum * 24 * 60 * 60;
			int lastBetDay = oneNum + subNum;

			Callable<List<IotDataVO>> subEndTask = () -> executeSingleRequest2(equipName, tagStr, url, configVO,
					lastStartSec, lastBetDay);
			subFutures[loopNum] = subExecutor.submit(subEndTask);

			// 收集所有子线程的结果集到一个集合中
			List<IotDataVO> mapVOs = new ArrayList<>();
			for (Future<List<IotDataVO>> future : subFutures) {
				if (future != null) {
					try {
						List<IotDataVO> tableData = future.get();
						if (tableData != null && !tableData.isEmpty()) {
							mapVOs.addAll(tableData);
						}
					} catch (Exception e) {
						logger.error("==主分支进程开启并执行子线程异常==", e);
					}
				}
			}
			// 关闭线程池（不再接受新任务）
			subExecutor.shutdown();

			// 组装当前参数一个月的数据量
			ManyParamVO allParam = new ManyParamVO();
			allParam.setTagName(tagStr);
			allParam.setDataVo(mapVOs);
			allSheetDatas.add(allParam);
		}

		return allSheetDatas;
	}

	/**
	 * 按照要创建的excel数量，创建线程，可以视为是主线程
	 * 
	 * @param equipName 设备名称字符串
	 * @param tagStrs   设备的所有标签
	 * @param tmpPathT  带有设备名称的路径
	 * @param saet      设备信息
	 * @param configVO  配置类
	 * @return boolean
	 */
	private boolean sendHttpsRequest(String equipName, List<String> tagStrs, String tmpPathT,
			StartAndEndTime saet, ApikeyVO configVO) {
		String tmpPath = tmpPathT + saet.getDateStr() + "_临时.xlsx";
		String endPath = tmpPathT + saet.getDateStr() + ".xlsx";
		logger.info("线程-" + saet.getDateStr() + "-执行https请求……");
		// 格式化时间为时间戳
		DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
		// 解析输入的时间字符串
		LocalDateTime startDT = LocalDateTime.parse(saet.getStartT(), dtf);
		ZonedDateTime zsdt = startDT.atZone(chinaZone);// 范围开始时间的时间戳
		Instant intst = zsdt.toInstant();
		long startSec = intst.getEpochSecond();

		LocalDateTime endDT = LocalDateTime.parse(saet.getEndT(), dtf);
		ZonedDateTime zedt = endDT.atZone(chinaZone);// 范围终止时间的时间戳
		Instant intet = zedt.toInstant();
		long endSec = intet.getEpochSecond();

		// 循环内发起请求
		String url = "https://10.101.1.200/reader/api/v1/historyDataByTag";

		ExecutorService subExecutor = Executors.newFixedThreadPool(SUB_THREADS_PER_MAIN);
		Future<List<ManyParamVO>>[] subFutures = new Future[SUB_THREADS_PER_MAIN];

		// 分发162个请求到4个子线程
		int subNum = TOTAL_REQUESTS % SUB_THREADS_PER_MAIN;
		int oneNum = (TOTAL_REQUESTS - subNum) / SUB_THREADS_PER_MAIN;
		int loopNum = SUB_THREADS_PER_MAIN - 1;
		for (int i = 0; i < loopNum; i++) {
			int tagStart = i * oneNum;
			int tagEnd = tagStart + oneNum;
			List<String> subTags = tagStrs.subList(tagStart, tagEnd);

			Callable<List<ManyParamVO>> subTask = () -> executeSingleRequest(equipName, subTags, url, configVO,
					startSec, endSec);
			subFutures[i] = subExecutor.submit(subTask);
		}
		int tagStart = loopNum * oneNum;
		int tagEnd = tagStart + oneNum + subNum;
		List<String> subTags = tagStrs.subList(tagStart, tagEnd);

		Callable<List<ManyParamVO>> subTask = () -> executeSingleRequest(equipName, subTags, url, configVO, startSec,
				endSec);
		subFutures[loopNum] = subExecutor.submit(subTask);

		List<ManyParamVO> mapVOs = new ArrayList<>();
		for (Future<List<ManyParamVO>> future : subFutures) {
			if (future != null) {
				try {
					List<ManyParamVO> tableData = future.get();
					if (tableData != null && !tableData.isEmpty()) {
						mapVOs.addAll(tableData);
					}
				} catch (Exception e) {
					logger.error("==主分支进程开启并执行子线程异常==", e);
				}
			}
		}
		// 关闭线程池（不再接受新任务）
		subExecutor.shutdown();

		new ArrayList<>();
		MutileDataVO mudVO = new MutileDataVO();
		if (!mapVOs.isEmpty()) {
			if (StringUtils.isNotBlank(saet.getDateStr())) {
				logger.info("线程-" + saet.getDateStr() + "-执行表格数据处理……");
				// 自定义表头
				List<List<String>> heads = new ArrayList<>();
				// 添加表头行：tagName + null
				for (ManyParamVO mP : mapVOs) {
					List<String> h_1 = new ArrayList<>();
					h_1.add(mP.getTagName());
					h_1.add("时间");
					heads.add(h_1);
					List<String> h_2 = new ArrayList<>();
					h_2.add("");
					h_2.add("数值");
					heads.add(h_2);
				}
				mudVO.setHeads(heads);

				// 纯数据表最大行数
				int rownum = 0;
				for (ManyParamVO manyParam : mapVOs) {
					int n = manyParam.getDataVo().size();
					if (rownum < n) {
						int tmp = rownum;
						rownum = n;
						n = tmp;
					}
				}

				List<List<Object>> afterAligns = alignColumns(mapVOs, rownum);
				mudVO.setAfterAligns(afterAligns);
				logger.info("线程-" + saet.getDateStr() + "-表格数据封装完成");
			}
		}
		mudVO.setSheetName(saet.getDateStr());

		// 遍历解析所有响应
		generateBaseExcel(mudVO, tmpPath);
		try {
			processMergedCells(tmpPath, endPath);
			return true;
		} catch (Exception e) {
			logger.error("==主分支进程遍历解析所有响应异常==", e);
		}
		return false;
	}

	/**
	 * 单次执行请求，一个线程执行多少天，按照天数计算终止时间，并且将当前参数的每一天作为一个双列数据，解决excel最大行数限制
	 *
	 * @param equipName  设备名称
	 * @param tagStr     本次循环的指标名称
	 * @param url        基础链接
	 * @param configVO   配置类，主要存储token等
	 * @param startSec   开始时间戳
	 * @param betweenDay 分配的天数
	 * @return {@link List}{@code <}{@link IotDataVO}{@code >} 两列数据组成的对象集合
	 */
	private List<ManyParamVO> executeSingleRequest3(String equipName, String tagStr, String url, ApikeyVO configVO,
												  long startSec, int betweenDay) {
		logger.info("==进入请求进程==");
		int len = betweenDay;
		String indexStr = equipName + "@" + tagStr;
		String tags = URLEncoder.encode(indexStr, StandardCharsets.UTF_8);
		List<ManyParamVO> mapVOs = new ArrayList<>();

		for (int i = 0; i < len; i++) {
			long endSec = startSec + (i + 1) * 24 * 60 * 60;
			String reqUri = url + "?tags=" + tags + "&startTime=" + startSec + "&endTime=" + endSec
					+ "&interval=&limit=" + 10000000L;

			// 2. 创建响应处理器（定义如何处理服务器响应）
			HttpClientResponseHandler<String> responseHandler = response -> {
				int statusCode = response.getCode();
				if (statusCode >= 200 && statusCode < 300) {
					HttpEntity entity = response.getEntity();
					try {
						String resJson = EntityUtils.toString(entity);
						EntityUtils.consume(entity); // &#9989; 显式消费实体
						return resJson;
					} catch (IOException e) {
						throw new RuntimeException("Failed to read response", e);
					}
				} else {
					return "";
				}
			};

			// 3. 发起请求并处理响应
			try {
				HttpGet hg = new HttpGet(reqUri);
				hg.setHeader("Authorization", configVO.getApikey());
				hg.setHeader("Content-Type", "application/json;charset=UTF-8");
				hg.setConfig(RequestConfig.custom().setConnectionRequestTimeout(CONNECT_TIMEOUT, TimeUnit.MILLISECONDS)
						.setResponseTimeout(RESPONSE_TIMEOUT, TimeUnit.MILLISECONDS).build());

				String resp = httpClient.execute(hg, responseHandler);
				if (StringUtils.isNotBlank(resp)) {
					JSONObject rootObj = JSONObject.parseObject(resp);
					if (rootObj != null) {
						JSONObject dataObj = rootObj.getJSONObject("data");
						if (dataObj != null) {
							JSONObject itemsObj = dataObj.getJSONObject("items");
							if (itemsObj != null && !itemsObj.isEmpty()) {
								JSONArray dataArr = itemsObj.getJSONArray(indexStr);
								if (dataArr != null && !dataArr.isEmpty()) {
									List<IotDataVO> dataVos = new ArrayList<>();
									ManyParamVO mP = new ManyParamVO();
									for (Object object : dataArr) {
										JSONObject objs = (JSONObject) object;
										long tiLon = objs.getLong("timestamp");
										Instant tiIn = Instant.ofEpochMilli(tiLon);
										LocalDateTime ltime = LocalDateTime.ofInstant(tiIn, chinaZone);
										BigDecimal dc = objs.getBigDecimal("value");
										IotDataVO dataVo = new IotDataVO();
										dataVo.setTs(ltime);
										dataVo.setVl(dc);
										dataVos.add(dataVo);
									}
									mP.setDataVo(dataVos);
									// 以时间戳为双列名
									mP.setTagName("时间范围" + endSec);
									mapVOs.add(mP);
								}
							}
						}
					}
				}
			} catch (Exception e) {
				logger.error("==请求进程发起请求或处理报文异常==", e);
			}
			startSec = endSec + 1;
		}
		return mapVOs;
	}

	/**
	 * 单次执行请求，一个线程执行多少天，按照天数计算终止时间
	 * 
	 * @param equipName  设备名称
	 * @param tagStr     本次循环的指标名称
	 * @param url        基础链接
	 * @param configVO   配置类，主要存储token等
	 * @param startSec   开始时间戳
	 * @param betweenDay 分配的天数
	 * @return {@link List}{@code <}{@link IotDataVO}{@code >} 两列数据组成的对象集合
	 */
	private List<IotDataVO> executeSingleRequest2(String equipName, String tagStr, String url, ApikeyVO configVO,
			long startSec, int betweenDay) {
		logger.info("==进入请求进程==");
		int len = betweenDay;
		String indexStr = equipName + "@" + tagStr;
		String tags = URLEncoder.encode(indexStr, StandardCharsets.UTF_8);
		List<IotDataVO> dataVos = new ArrayList<>();
		for (int i = 0; i < len; i++) {
			long endSec = startSec + (i + 1) * 24 * 60 * 60;
			String reqUri = url + "?tags=" + tags + "&startTime=" + startSec + "&endTime=" + endSec
					+ "&interval=&limit=" + 10000000L;

			// 2. 创建响应处理器（定义如何处理服务器响应）
			HttpClientResponseHandler<String> responseHandler = response -> {
				int statusCode = response.getCode();
				if (statusCode >= 200 && statusCode < 300) {
					HttpEntity entity = response.getEntity();
					try {
						String resJson = EntityUtils.toString(entity);
						EntityUtils.consume(entity); // &#9989; 显式消费实体
						return resJson;
					} catch (IOException e) {
						throw new RuntimeException("Failed to read response", e);
					}
				} else {
					return "";
				}
			};

			// 3. 发起请求并处理响应
			try {
				HttpGet hg = new HttpGet(reqUri);
				hg.setHeader("Authorization", configVO.getApikey());
				hg.setHeader("Content-Type", "application/json;charset=UTF-8");
				hg.setConfig(RequestConfig.custom().setConnectionRequestTimeout(CONNECT_TIMEOUT, TimeUnit.MILLISECONDS)
						.setResponseTimeout(RESPONSE_TIMEOUT, TimeUnit.MILLISECONDS).build());

				String resp = httpClient.execute(hg, responseHandler);
				if (StringUtils.isNotBlank(resp)) {
					JSONObject rootObj = JSONObject.parseObject(resp);
					if (rootObj != null) {
						JSONObject dataObj = rootObj.getJSONObject("data");
						if (dataObj != null) {
							JSONObject itemsObj = dataObj.getJSONObject("items");
							if (itemsObj != null && !itemsObj.isEmpty()) {
								JSONArray dataArr = itemsObj.getJSONArray(indexStr);
								if (dataArr != null && !dataArr.isEmpty()) {
									for (Object object : dataArr) {
										JSONObject objs = (JSONObject) object;
										long tiLon = objs.getLong("timestamp");
										Instant tiIn = Instant.ofEpochMilli(tiLon);
										LocalDateTime ltime = LocalDateTime.ofInstant(tiIn, chinaZone);
										BigDecimal dc = objs.getBigDecimal("value");
										IotDataVO dataVo = new IotDataVO();
										dataVo.setTs(ltime);
										dataVo.setVl(dc);
										dataVos.add(dataVo);
									}
								}
							}
						}
					}
				}
			} catch (Exception e) {
				logger.error("==请求进程发起请求或处理报文异常==", e);
			}
		}
		return dataVos;
	}

	/**
	 * 单次执行请求
	 * 
	 * @param equipName 设备名称
	 * @param tagStrs   本次循环的指标名称
	 * @param url       基础链接
	 * @param configVO  配置类，主要存储token等
	 * @param startSec  开始时间戳
	 * @param endSec    结束时间戳
	 * @return {@link ManyParamVO} 两列数据组成的对象
	 */
	private List<ManyParamVO> executeSingleRequest(String equipName, List<String> tagStrs, String url,
			ApikeyVO configVO, long startSec, long endSec) {
		logger.info("==进入请求进程==");
		int len = tagStrs.size();
		List<ManyParamVO> result = new ArrayList<>();
		for (String tagStr : tagStrs) {
			ManyParamVO manpVO = new ManyParamVO();
			manpVO.setTagName(tagStr);

			String indexStr = equipName + "@" + tagStr;
			String reqUri = url + "?tags=" + URLEncoder.encode(indexStr, StandardCharsets.UTF_8) + "&startTime="
					+ startSec + "&endTime=" + endSec + "&interval=&limit=" + 1000000L;

			// 2. 创建响应处理器（定义如何处理服务器响应）
			HttpClientResponseHandler<String> responseHandler = response -> {
				int statusCode = response.getCode();
				if (statusCode >= 200 && statusCode < 300) {
					HttpEntity entity = response.getEntity();
					try {
						String resJson = EntityUtils.toString(entity);
						EntityUtils.consume(entity); // &#9989; 显式消费实体
						return resJson;
					} catch (IOException e) {
						throw new RuntimeException("Failed to read response", e);
					}
				} else {
					return "";
				}
			};

			try {
				HttpGet hg = new HttpGet(reqUri);
				hg.setHeader("Authorization", configVO.getApikey());
				hg.setHeader("Content-Type", "application/json;charset=UTF-8");
				hg.setConfig(RequestConfig.custom().setConnectionRequestTimeout(CONNECT_TIMEOUT, TimeUnit.MILLISECONDS)
						.setResponseTimeout(RESPONSE_TIMEOUT, TimeUnit.MILLISECONDS).build());

				String resp = httpClient.execute(hg, responseHandler);
				if (StringUtils.isNotBlank(resp)) {
					JSONObject rootObj = JSONObject.parseObject(resp);
					if (rootObj != null) {
						JSONObject dataObj = rootObj.getJSONObject("data");
						if (dataObj != null) {
							JSONObject itemsObj = dataObj.getJSONObject("items");
							if (itemsObj != null && !itemsObj.isEmpty()) {
								JSONArray dataArr = itemsObj.getJSONArray(indexStr);
								if (dataArr != null && !dataArr.isEmpty()) {
									List<IotDataVO> dataVos = new ArrayList<>();
									for (Object object : dataArr) {
										JSONObject objs = (JSONObject) object;
										long tiLon = objs.getLong("timestamp");
										Instant tiIn = Instant.ofEpochMilli(tiLon);
										LocalDateTime ltime = LocalDateTime.ofInstant(tiIn, chinaZone);
										BigDecimal dc = objs.getBigDecimal("value");
										IotDataVO dataVo = new IotDataVO();
										dataVo.setTs(ltime);
										dataVo.setVl(dc);
										dataVos.add(dataVo);
									}
									manpVO.setDataVo(dataVos);
									result.add(manpVO);
								}
							}
						}
					}
				}
			} catch (Exception e) {
				logger.error("==请求进程发起请求或处理报文异常==", e);
			}
		}
		return result;
	}
	
	// 第一步：生成基础 Excel（不含合并）
		public void generateBaseExcel2(List<MutileDataVO> sheetDatas, String filePath) {
			if (sheetDatas!=null && !sheetDatas.isEmpty()) {
				ExcelWriter excelWriter = null;
				try {
					Path fip = Paths.get(filePath);
					// 删除已存在的文件（如果存在）
					// 创建新文件
					Files.deleteIfExists(fip);
					Files.createFile(fip);
					FileOutputStream fos = new FileOutputStream(fip.toFile());
					ExcelWriterBuilder writerBuilder = EasyExcel.write(fos).excelType(ExcelTypeEnum.XLSX)
							.charset(Charset.forName("GBK")).autoCloseStream(true);
					excelWriter = writerBuilder.build();
					for (MutileDataVO sheetData : sheetDatas) {
						if (sheetData != null && sheetData.getAfterAligns() != null) {
							if (!sheetData.getAfterAligns().isEmpty()) {
								WriteSheet writeSheet = EasyExcel.writerSheet(sheetData.getSheetName()).head(sheetData.getHeads())
										.build();
								excelWriter.write(sheetData.getAfterAligns(), writeSheet);
							}
						}
					}
				} catch (Exception e) {
					logger.error(e);
				} finally {
					if (excelWriter != null) {
						excelWriter.finish();
					}
				}
				
			}
		}

	// 第一步：生成基础 Excel（不含合并）
	public void generateBaseExcel(MutileDataVO sheetData, String filePath) {
		if (sheetData != null && sheetData.getAfterAligns() != null) {
			if (!sheetData.getAfterAligns().isEmpty()) {
				ExcelWriter excelWriter = null;
				try {
					Path fip = Paths.get(filePath);
					// 删除已存在的文件（如果存在）
					// 创建新文件
					Files.deleteIfExists(fip);
					Files.createFile(fip);
					FileOutputStream fos = new FileOutputStream(fip.toFile());
					ExcelWriterBuilder writerBuilder = EasyExcel.write(fos).excelType(ExcelTypeEnum.XLSX)
							.charset(Charset.forName("GBK")).autoCloseStream(true);
					excelWriter = writerBuilder.build();
					WriteSheet writeSheet = EasyExcel.writerSheet(sheetData.getSheetName()).head(sheetData.getHeads())
							.build();
					excelWriter.write(sheetData.getAfterAligns(), writeSheet);
				} catch (Exception e) {
					logger.error(e);
				} finally {
					if (excelWriter != null) {
						excelWriter.finish();
					}
				}
			}
		}
	}

	// 合并处理整个sheet页
	public List<List<Object>> alignColumns(List<ManyParamVO> mpvos, int maxRows) {
		if (mpvos == null || mpvos.isEmpty()) {
			return new ArrayList<>();
		}
		// 初始化结果集（行数 = maxRows）
		List<List<Object>> result = new ArrayList<>(maxRows);
		int doubleColumnNum = mpvos.size();
		for (int i = 0; i < maxRows; i++) {
			result.add(new ArrayList<>());
		}
		// 逐行填充数据
		for (int a1 = 0; a1 < doubleColumnNum; a1++) {
			ManyParamVO manyp = mpvos.get(a1);
			List<IotDataVO> vos = manyp.getDataVo();
			int dataLen = vos.size();
			for (int a2 = 0; a2 < maxRows; a2++) {
				if (a2 < dataLen) {
					IotDataVO vo = vos.get(a2);
					// 存在有效数据，添加到对应行
					result.get(a2).add(vo.getTs());
					result.get(a2).add(vo.getVl());
				} else {
					result.get(a2).add("");
					result.get(a2).add("");
				}
			}
		}
		return result;
	}

	/**
	 * 处理单元格合并逻辑
	 * 
	 * @param inputFilePath  临时文件路径
	 * @param outputFilePath 新输出文件路径
	 * @throws Exception IO异常
	 */
	public void processMergedCells(String inputFilePath, String outputFilePath) throws Exception {
		Path fip = Paths.get(inputFilePath);
		if (Files.exists(fip) && Files.size(fip) > 0) {
			FileInputStream fn = new FileInputStream(inputFilePath);
			try (Workbook workbook = WorkbookFactory.create(fn)) {
				for (int i = 0; i < workbook.getNumberOfSheets(); i++) {
					Sheet sheet = workbook.getSheetAt(i);
					Row headerRow = sheet.getRow(0); // 获取表头行

					// 遍历表头行的单元格（步长为2）
					for (int j = 0; j < headerRow.getLastCellNum(); j += 2) {
						Cell leftCell = headerRow.getCell(j);
						Cell rightCell = headerRow.getCell(j + 1);

						if (leftCell != null && !leftCell.toString().isEmpty()
								&& (rightCell == null || rightCell.toString().isEmpty())) {
							// 合并单元格
							sheet.addMergedRegion(new CellRangeAddress(leftCell.getRowIndex(), leftCell.getRowIndex(),
									leftCell.getColumnIndex(), rightCell.getColumnIndex()));

							// 将值写入合并后的单元格
							leftCell.setCellValue(leftCell.toString());
							// 清空右侧单元格
							rightCell.setBlank();
						}
					}
				}

				// 写入最终文件
				Path fop = Paths.get(outputFilePath);
				// 删除已存在的文件（如果存在）
				// 创建新文件
				Files.deleteIfExists(fop);
				Files.createFile(fop);
				try (FileOutputStream out = new FileOutputStream(fop.toFile())) {
					workbook.write(out);
				}
			}
		} else {
			throw new Exception("文件为空！");
		}
	}

	/**
	 * 初始化连接池
	 * 
	 * @return CloseableHttpClient
	 */
	private static CloseableHttpClient createUnsafeHttpClient() {
		try {
			// 创建无条件信任的SSL上下文
			SSLContext sc = SSLUtil.createAll();
			// 配置连接池管理器
			return HttpClients.custom()
					.setConnectionManager(PoolingHttpClientConnectionManagerBuilder.create()
							.setTlsSocketStrategy(new DefaultClientTlsStrategy(sc, NoopHostnameVerifier.INSTANCE))
							.setMaxConnTotal(CONNECTION_POOL_SIZE)
							// 设置每主机最大连接数
							.setMaxConnPerRoute(CONNECTIONS_PER_HOST)
							.setDefaultConnectionConfig(
									ConnectionConfig.custom().setConnectTimeout(CONNECT_TIMEOUT, TimeUnit.MILLISECONDS)
											.setSocketTimeout(RESPONSE_TIMEOUT, TimeUnit.MILLISECONDS)
											.setValidateAfterInactivity(5000, TimeUnit.MILLISECONDS).build())
							.build())
					.build();
		} catch (Exception e) {
			throw new RuntimeException("创建HTTP client失败", e);
		}
	}

	// 跳过SSL证书验证
	private static void trustAllCertificates(HttpsURLConnection connection) throws Exception {
		SSLContext sc = SSLUtil.createAll();
		connection.setSSLSocketFactory(sc.getSocketFactory());
		connection.setHostnameVerifier(new TrustAllHostnameVerifier()); // 禁用主机名验证
	}

	/**
	 * 测试网络连通性
	 *
	 * @param urlString 待测试网址
	 * @return boolean
	 */
	public static boolean testConnectivity(String urlString) {
		try {
			logger.info("检测与服务端网络连接……");
			URL url = new URL(urlString);
			HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
			trustAllCertificates(connection);
			connection.setRequestMethod("GET");
			connection.setConnectTimeout(5000); // 设置连接超时时间（毫秒）
			connection.setReadTimeout(5000); // 设置读取超时时间（毫秒）
			int responseCode = connection.getResponseCode();
			return (200 <= responseCode && responseCode <= 299); // 检查响应码是否在2xx范围内
		} catch (Exception e) {
			logger.error(e);
			return false;
		}
	}
}
